//
//  PageViewController.swift
//  ASW
//
//  Created by zhuxuhong on 2020/12/28.
//

import UIKit

public typealias PagerDelegate = UIPageViewControllerDelegate
public typealias PagerDataSource = UIPageViewControllerDataSource
public typealias PagerOrientation = UIPageViewController.NavigationOrientation
public typealias PagerDirection = UIPageViewController.NavigationDirection
public typealias PagerOptions = [UIPageViewController.OptionsKey: Any]
public typealias PagerSpineLocation = UIPageViewController.SpineLocation

public protocol DynamicPages: NormalPages {
    func indexForPage(_ page: UIViewController) -> Int
    func pageAtIndex(_ index: Int) -> UIViewController?
}

extension DynamicPages {
    public var pageIndex: Int {
        return indexForPage(currentPage)
    }
}

public protocol StaticPages: NormalPages {
    var pageVCs: [UIViewController] {get}
}
extension StaticPages {
    public var pageIndex: Int {
        return pageVCs.firstIndex(of: currentPage) ?? 0
    }
    public var numberOfPages: Int {
        return pageVCs.count
    }
}

public protocol NormalPages where Self: PageViewController {
    var pageIndex: Int {get}
    var numberOfPages: Int {get}
    var autoBouncing: Bool {get}
}

extension NormalPages {
    public var autoBouncing: Bool{
        return false
    }
}

public protocol CustomPages where Self: UIPageViewController{
    func pageBefore(page vc: UIViewController) -> UIViewController?
    func pageAfter(page vc: UIViewController) -> UIViewController?
}

open class PageViewController: UIPageViewController, ViewControllerAdapter {
    public struct Style {
        var transition: TransitionStyle!
        var orientation: NavigationOrientation!
        var options: PagerOptions?
        init(
            transition: TransitionStyle = .scroll,
            orientation: NavigationOrientation = .horizontal,
            options: PagerOptions? = nil
        ) {
            self.transition = transition
            self.orientation = orientation
            self.options = options
        }
    }
    public var currentPage: UIViewController! {
        didSet{
            pageDidChange(page: currentPage)
        }
    }
    public var scrollView: UIScrollView? {
        return view.subviews.first{ $0 is UIScrollView } as? UIScrollView
    }
    open func pageDidChange(page vc: UIViewController) {
    }
    
    public func scrollToPage(_ page: UIViewController, animated: Bool = true) {
        setPages([page], animated: animated)
    }
    public func setPages(
        _ pages: [UIViewController],
        animated: Bool = true,
        completion: ((Bool)->Void)? = nil
    ){
        setViewControllers(pages, direction: .forward, animated: animated) { [weak self] finished in
            if finished {
                self?.currentPage = self?.viewControllers?.first
            }
            completion?(finished)
        }
    }
    public func reloadPages(){
        setPages([currentPage])
    }
    
    convenience init() {
        self.init(style: .init())
    }
    
    convenience init(style: Style) {
        self.init(transitionStyle: style.transition, navigationOrientation: style.orientation, options: style.options)
    }
    
    public override init(
        transitionStyle style: TransitionStyle,
        navigationOrientation: NavigationOrientation,
        options: PagerOptions? = nil
    ) {
        super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
        didInit()
    }
    
    required public init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }
    open override func awakeFromNib() {
        super.awakeFromNib()
        didAwake()
    }
    
    open override func viewDidLoad() {
        super.viewDidLoad()
        
        didLoad()
    }
    open override func updateViewConstraints() {
        super.updateViewConstraints()
        updateConstraints()
    }
    
    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        willAppear()
    }
    
    open override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        willLayout()
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        didLayout()
    }
    
    open override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        didAppear()
    }
    
    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        willDisapppear()
    }
    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        didDisappear()
    }
    
    deinit {
        willDeinit()
    }
    
    open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        willRotate(toSize: size, coordinator: coordinator)
    }
    open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        willRotate(toNewCollection: newCollection, coordinator: coordinator)
    }
    open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        didRotated(previousTrait: previousTraitCollection, currentTrait: traitCollection)
    }
    
    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        touchBegan(touch: touches.first!)
    }
    open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        touchEnded(touch: touches.first!)
    }
    open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        touchCancelled(touch: touches.first!)
    }
    open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        touchMoved(touch: touches.first!)
    }
    //MARK: ViewTouched
    open func touchBegan(touch: UITouch){}
    open func touchEnded(touch: UITouch){}
    open func touchCancelled(touch: UITouch){}
    open func touchMoved(touch: UITouch){}
    
    //MARK: ViewControllerAdapter
    open func didInit(){}
    open func didAwake(){}
    open func willDeinit(){}
    open func didLoad(){
        delegate = self
        dataSource = self
        if let _ = self as? NormalPages {
            scrollView?.delegate = self
        }
    }
    open func updateConstraints() {}
    open func willLayout(){}
    private var _oldBounds: CGRect?
    open func didLayout(){
        if let rect = _oldBounds, !rect.equalTo(view.bounds) {
            CATransaction.disableActions()
            CATransaction.begin()
            layoutDidChange()
            CATransaction.commit()
        }
        _oldBounds = view.bounds
    }
    open func layoutDidChange(){}
    open func willAppear(){}
    open func didAppear(){}
    open func willRotate(toSize: CGSize, coordinator: UIViewControllerTransitionCoordinator){}
    open func willRotate(toNewCollection: UITraitCollection, coordinator: UIViewControllerTransitionCoordinator){}
    open func didRotated(previousTrait: UITraitCollection?, currentTrait: UITraitCollection){}
    open func willDisapppear(){}
    open func didDisappear(){}
}

extension PageViewController: UIScrollViewDelegate {
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let vc = self as? NormalPages, vc.autoBouncing else {
            return
        }
        let idx = vc.pageIndex
        scrollView.bounces = idx == 0 || idx == vc.numberOfPages - 1
        ? false
        : true
    }
}

extension PageViewController: UIPageViewControllerDataSource {
    public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        if let vc = self as? CustomPages {
            return vc.pageBefore(page: viewController)
        }
        if let vc = self as? StaticPages,
           let idx = vc.pageVCs.firstIndex(of: viewController),
           idx > 0 {
            return vc.pageVCs[idx - 1]
        }
        if let vc = self as? DynamicPages {
            let idx = vc.indexForPage(viewController)
            if idx > 0 {
                return vc.pageAtIndex(idx - 1)
            }
        }
        return nil
    }
    public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        if let vc = self as? CustomPages {
            return vc.pageAfter(page: viewController)
        }
        if let vc = self as? StaticPages,
           let idx = vc.pageVCs.firstIndex(of: viewController),
           idx < vc.pageVCs.count-1 {
            return vc.pageVCs[idx + 1]
        }
        if let vc = self as? DynamicPages {
            let idx = vc.indexForPage(viewController)
            if idx < vc.numberOfPages - 1 {
                return vc.pageAtIndex(idx + 1)
            }
        }
        return nil
    }
}

extension PageViewController: UIPageViewControllerDelegate {
    public func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> PagerSpineLocation {
        return .min
    }
    public func pageViewController(
        _ pageViewController: UIPageViewController,
        didFinishAnimating finished: Bool,
        previousViewControllers: [UIViewController],
        transitionCompleted completed: Bool
    ) {
        if finished, completed,
           let page = viewControllers?.first,
            currentPage != page {
            currentPage = page
        }
    }
}
